package cn.sylinx.horm.proxy.mapper.invoker;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import cn.sylinx.horm.config.OrmConfigHolder;
import cn.sylinx.horm.core.common.Page;
import cn.sylinx.horm.core.common.Record;
import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.proxy.mapper.MapperMethodMetadata;
import cn.sylinx.horm.proxy.mapper.interceptor.MapperInvokerProxy;
import cn.sylinx.horm.proxy.mapper.parse.NamespaceContainer;
import cn.sylinx.horm.proxy.mapper.parse.SqlItem;
import cn.sylinx.horm.proxy.mapper.parse.SqlType;
import cn.sylinx.horm.util.Tuple;

public enum MapperInvokerManager {
    ;
    private static Set<Class<?>> primaryOrMappedWrapTypeOrFrequentType = new HashSet<>(8);

    static {
        primaryOrMappedWrapTypeOrFrequentType.add(Boolean.class);
        primaryOrMappedWrapTypeOrFrequentType.add(boolean.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Byte.class);
        primaryOrMappedWrapTypeOrFrequentType.add(byte.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Character.class);
        primaryOrMappedWrapTypeOrFrequentType.add(char.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Short.class);
        primaryOrMappedWrapTypeOrFrequentType.add(short.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Integer.class);
        primaryOrMappedWrapTypeOrFrequentType.add(int.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Long.class);
        primaryOrMappedWrapTypeOrFrequentType.add(long.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Float.class);
        primaryOrMappedWrapTypeOrFrequentType.add(float.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Double.class);
        primaryOrMappedWrapTypeOrFrequentType.add(double.class);
        primaryOrMappedWrapTypeOrFrequentType.add(String.class);
        primaryOrMappedWrapTypeOrFrequentType.add(BigInteger.class);
        primaryOrMappedWrapTypeOrFrequentType.add(BigDecimal.class);
        primaryOrMappedWrapTypeOrFrequentType.add(Date.class);
    }

    private static final Map<String, MapperInvoker> mapperInvokerMaps = new HashMap<>();
    private static final Map<String, MapperInvoker> cachedmapperInvokerMaps = new HashMap<>();

    static {
        // key rules: "type."
        String insertKey = "INSERT.";
        mapperInvokerMaps.put(insertKey, maybeProxyed(new InsertMapperInvoker()));
        String deleteKey = "DELETE.";
        mapperInvokerMaps.put(deleteKey, maybeProxyed(new DeleteMapperInvoker()));
        String updateKey = "UPDATE.";
        mapperInvokerMaps.put(updateKey, maybeProxyed(new UpdateMapperInvoker()));
        String executeKey = "EXECUTE.";
        mapperInvokerMaps.put(executeKey, maybeProxyed(new ExecuteMapperInvoker()));
        String queryKey1 = "QUERY.LIST.BEAN";
        mapperInvokerMaps.put(queryKey1, maybeProxyed(new BeanListMapperInvoker()));
        String queryKey2 = "QUERY.LIST.RECORD";
        mapperInvokerMaps.put(queryKey2, maybeProxyed(new RecordListMapperInvoker()));
        String queryKey3 = "QUERY.LIST.OBJ";
        mapperInvokerMaps.put(queryKey3, maybeProxyed(new ObjectListMapperInvoker()));
        String queryKey4 = "QUERY.PAGE.BEAN";
        mapperInvokerMaps.put(queryKey4, maybeProxyed(new BeanPageMapperInvoker()));
        String queryKey5 = "QUERY.PAGE.RECORD";
        mapperInvokerMaps.put(queryKey5, maybeProxyed(new RecordPageMapperInvoker()));
        String queryKey6 = "QUERY.ONE.BEAN";
        mapperInvokerMaps.put(queryKey6, maybeProxyed(new BeanQueryOneMapperInvoker()));
        String queryKey7 = "QUERY.ONE.RECORD";
        mapperInvokerMaps.put(queryKey7, maybeProxyed(new RecordQueryOneMapperInvoker()));
        String queryKey8 = "QUERY.ONE.OBJ";
        mapperInvokerMaps.put(queryKey8, maybeProxyed(new ObjectQueryOneMapperInvoker()));
    }

    private static MapperInvoker maybeProxyed(MapperInvoker mapperInvoker) {
        return OrmConfigHolder.hasMapperInterceptor() ? MapperInvokerProxy.create(mapperInvoker) : mapperInvoker;
    }

    public static MapperInvoker getBy(MapperMethodMetadata methodMetadata) {

        String metaKey = methodMetadata.getMetaKey();
        if (cachedmapperInvokerMaps.containsKey(metaKey)) {
            return cachedmapperInvokerMaps.get(metaKey);
        }
        String mapperInvokerKey = parseMatchKey(methodMetadata);
        MapperInvoker mapperInvoker = mapperInvokerMaps.get(mapperInvokerKey);
        if (mapperInvoker == null) {
            throw new HORMException("no suitable mapper invoker");
        }
        // 缓存起来，不再次经过计算
        cachedmapperInvokerMaps.put(metaKey, mapperInvoker);
        return mapperInvoker;
    }

    private static String parseMatchKey(MapperMethodMetadata methodMetadata) {

        Tuple t = NamespaceContainer.getMapper(methodMetadata.getMapperInterface(), methodMetadata.getMethodName());

        SqlItem sqlItem = t.getObject(1);

        Class<?> returnType = methodMetadata.getReturnType();
        Class<?> truelyReturnType = methodMetadata.getTruelyReturnType();

        // 查询 ?
        if (isQuery(sqlItem)) {
            StringBuilder sb = new StringBuilder();
            sb.append("QUERY.");
            // 列表 ?
            if (returnType == List.class) {
                sb.append("LIST.");
                // BEAN ?
                if (!isPrimaryOrMappedWrapTypeOrFrequentType(truelyReturnType)
                        && !isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("BEAN");
                    return sb.toString();
                }
                // Record ?
                if (isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("RECORD");
                    return sb.toString();
                }
                // Object ?
                if (isPrimaryOrMappedWrapTypeOrFrequentType(truelyReturnType)) {
                    sb.append("OBJ");
                    return sb.toString();
                }
            }
            // 分页 ?
            if (returnType == Page.class) {
                sb.append("PAGE.");
                // Record ?
                if (isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("RECORD");
                    return sb.toString();
                }
                // Bean ?
                if (!isRecordForTruelyReturnType(truelyReturnType)) {
                    sb.append("BEAN");
                    return sb.toString();
                }
                // Object ? 暂时不支持
            }
            if (returnType != List.class && returnType != Page.class) {
                // 不是列表、不是分页
                sb.append("ONE.");
                // BEAN ?
                if (!isPrimaryOrMappedWrapTypeOrFrequentType(returnType) && !isRecordForTruelyReturnType(returnType)) {
                    sb.append("BEAN");
                    return sb.toString();
                }
                // Record ?
                if (isRecordForTruelyReturnType(returnType)) {
                    sb.append("RECORD");
                    return sb.toString();
                }
                // Object
                if (isPrimaryOrMappedWrapTypeOrFrequentType(returnType)) {
                    sb.append("OBJ");
                    return sb.toString();
                }
            }
        }
        if (isUpdate(sqlItem)) {
            return "UPDATE.";
        }
        if (isExecute(sqlItem)) {
            return "EXECUTE.";
        }
        if (isInsert(sqlItem)) {
            return "INSERT.";
        }
        if (isDelete(sqlItem)) {
            return "DELETE.";
        }

        return "NONE";
    }

    /**
     * 是否是 cn.sylinx.hbatis.db.common.Record 类型
     * 
     * @return
     */
    private static boolean isRecordForTruelyReturnType(Class<?> truelyReturnType) {
        return truelyReturnType == Record.class;
    }

    /**
     * 是否是8种原始类型或者对应的封装类型或者是常用类型
     * 
     * @return
     */
    private static boolean isPrimaryOrMappedWrapTypeOrFrequentType(Class<?> truelyReturnType) {
        return primaryOrMappedWrapTypeOrFrequentType.contains(truelyReturnType);
    }

    // 是查询
    private static boolean isQuery(SqlItem sqlItem) {
        return SqlType.select == sqlItem.getSqlType();
    }

    private static boolean isInsert(SqlItem sqlItem) {
        return SqlType.insert == sqlItem.getSqlType();
    }

    private static boolean isUpdate(SqlItem sqlItem) {
        return SqlType.update == sqlItem.getSqlType();
    }

    private static boolean isExecute(SqlItem sqlItem) {
        return SqlType.execute == sqlItem.getSqlType();
    }

    private static boolean isDelete(SqlItem sqlItem) {
        return SqlType.delete == sqlItem.getSqlType();
    }
}